package org.mx;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

/**
 * 日期处理工具类，包括常用的日期数据处理的方法。
 *
 * @author ： john peng date ： 2016/05/31
 */
public class DateUtils {
	/**
	 * 定义星期的第一天，默认为：周一；如果设置为false，则为周日。
	 */
	public static boolean FIRST_DAY_IS_MONDAY = true;
	/**
	 * 定义一月是否为0，默认为0；如果设置为false，则一月为1。
	 */
	public static boolean JANUARY_IS_ZERO = true;

	/**
	 * 默认的构造函数
	 */
	private DateUtils() {
		super();
	}

	/**
	 * 将一个Date对象转换为Instant对象
	 * 
	 * @param date Date对象
	 * @return Instant对象
	 */
	public static Instant from(Date date) {
		return Instant.ofEpochMilli(date.getTime());
	}

	/**
	 * 将一个Instant对象转换为Date对象
	 * 
	 * @param instant Instant对象
	 * @return Date对象
	 */
	public static Date from(Instant instant) {
		return new Date(instant.toEpochMilli());
	}

	/**
	 * 将一个LocalDate对象转换为Date对象
	 * 
	 * @param localDate LocalDate对象
	 * @return Date对象
	 */
	public static Date from(LocalDate localDate) {
		return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
	}

	/**
	 * 将一个LocalDateTime对象转换为Date对象
	 * 
	 * @param localDateTime LocalDateTime对象
	 * @return Date对象
	 */
	public static Date from(LocalDateTime localDateTime) {
		return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
	}

	/**
	 * 获取指定的日期对象中的字段
	 * 
	 * @param date  日期对象
	 * @param field 字段枚举
	 * @return 日期对象中指定字段值
	 */
	public static int get(Date date, FieldType field) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()));
		calendar.setTimeInMillis(date.getTime());
		switch (field) {
		case YEAR:
			return calendar.get(Calendar.YEAR);
		case MONTH:
			return calendar.get(Calendar.MONTH) + 1;
		case DAY:
			return calendar.get(Calendar.DATE);
		case HOUR:
			return calendar.get(Calendar.HOUR_OF_DAY);
		case MINUTE:
			return calendar.get(Calendar.MINUTE);
		case SECOND:
			return calendar.get(Calendar.SECOND);
		case MILLISECOND:
			return calendar.get(Calendar.MILLISECOND);
		default:
			throw new UnsupportedOperationException(String.format("Unsupported field type: %s.", field));
		}
	}

	/**
	 * 将一个日期对象转换为LocalDate对象
	 * 
	 * @param date 日期对象
	 * @return LocalDate对象
	 */
	public static LocalDate toLocalDate(Date date) {
		return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalDate();
	}

	/**
	 * 将一个日期对象转换为LocalTime对象
	 * 
	 * @param date 日期对象
	 * @return LocalTime对象
	 */
	public static LocalTime toLocalTime(Date date) {
		return LocalDateTime.ofInstant(date.toInstant(), ZoneId.systemDefault()).toLocalTime();
	}

	/**
	 * 将一个日期对象转换为LocalDateTime对象
	 * 
	 * @param date 日期对象
	 * @return LocalDateTime对象
	 */
	public static LocalDateTime toLocalDateTime(Date date) {
		LocalDate localDate = toLocalDate(date);
		LocalTime localTime = toLocalTime(date);
		return LocalDateTime.of(localDate, localTime);
	}

	/**
	 * 在一个指定的日期对象上进行日期时间计算
	 *
	 * @param date   进行计算的基准日期对象
	 * @param field  日期时间计算的字段类型，支持：年、月、日、小时、分、秒的数据计算
	 * @param amount 需要计算的数量，如果是负数，表示相减
	 * @return 计算好的日期对象
	 */
	public static Date add(Date date, FieldType field, int amount) {
		LocalDateTime localDateTime = toLocalDateTime(date);
		localDateTime = add(localDateTime, field, amount);
		return from(localDateTime);
	}

	/**
	 * 在一个指定的日期时间对象上进行日期时间计算
	 *
	 * @param localDateTime 进行计算的基准日期时间对象
	 * @param field         日期时间计算的字段类型，支持：年、月、日、小时、分、秒的数据计算
	 * @param amount        需要计算的数量，如果是负数，表示相减
	 * @return 计算好的日期时间对象
	 */
	public static LocalDateTime add(LocalDateTime localDateTime, FieldType field, int amount) {
		switch (field) {
		case YEAR:
			localDateTime = localDateTime.plusYears(amount);
			break;
		case MONTH:
			localDateTime = localDateTime.plusMonths(amount);
			break;
		case DAY:
			localDateTime = localDateTime.plusDays(amount);
			break;
		case HOUR:
			localDateTime = localDateTime.plusHours(amount);
			break;
		case MINUTE:
			localDateTime = localDateTime.plusMinutes(amount);
			break;
		case SECOND:
			localDateTime = localDateTime.plusSeconds(amount);
			break;
		default:
			throw new UnsupportedOperationException(String.format("Unsupported field type: %s.", field));
		}
		return localDateTime;
	}

	/**
	 * 将持续时间数值转化为“#天#小时#分#秒”格式的描述
	 * @param elapseTime 持续时间
	 * @return 时间描述
	 */
	public static String formatElapseTime(int elapseTime) {
		int[] units = { 60, 60, 24 };
		int[] number = { 0, 0, 0, (int) (elapseTime / 24 / 3600) };
		elapseTime -= number[3] * 24 * 3600;
		String[] names = { "秒", "分", "小时", "天" };
		for (int index = 0; index < 3; index++) {
			number[index] = (int) (elapseTime % units[index]);
			elapseTime = (elapseTime - number[index]) / units[index];
		}
		StringBuffer sb = new StringBuffer();
		for (int index = 3; index >= 0; index--) {
			if (number[index] > 0) {
				sb.append(number[index]);
				sb.append(names[index]);
			}
		}
		return sb.toString();
	}

	/**
	 * 时间格式转换函数，转换为指定格式的时间字符串。
	 *
	 * @param date    待格式化的时间对象
	 * @param pattern 时间格式
	 * @return 输出的时间字符串
	 */
	public static String format(Date date, String pattern) {
		if (date == null) {
			return "";
		}
		DateTimeFormatter format = DateTimeFormatter.ofPattern(pattern);
		return format.format(toLocalDateTime(date));
	}

	/**
	 * 时间格式转换函数，转换为时间对象。
	 *
	 * @param dateString 待解析的时间字符串
	 * @param pattern    时间格式
	 * @return 输出的时间对象
	 */
	public static Date parse(String dateString, String pattern) {
		DateTimeFormatter format = DateTimeFormatter.ofPattern(pattern);
		TemporalAccessor temporalAccessor = format.parse(dateString);
		return from(LocalDateTime.from(temporalAccessor));
	}

	/**
	 * 将指定的日期转换为10位长度的日期字串，日期格式为：yyyy-MM-dd。
	 *
	 * @param date 日期对象
	 * @return 日期字串
	 */
	public static String get10Date(Date date) {
		return format(date, "yyyy-MM-dd");
	}

	/**
	 * 获得一个10位长度的当前日期字串
	 *
	 * @return 日期字串
	 * @see #get10Date(Date)
	 */
	public static String get10DateNow() {
		return get10Date(new Date());
	}

	/**
	 * 将指定的日期转换为12位长度的时间字串，日期格式为：HH:mm:ss.SSS。
	 *
	 * @param date 日期对象
	 * @return 时间字串
	 */
	public static String get12Time(Date date) {
		return format(date, "HH:mm:ss.n");
	}

	/**
	 * 获得一个12位长度的当前时间字串
	 *
	 * @return 时间字串
	 * @see #get12Time(Date)
	 */
	public static String get12TimeNow() {
		return get12Time(new Date());
	}

	/**
	 * 将指定的日期转换为19位长度的日期字串，日期格式为：yyyy-MM-dd HH:mm:ss。
	 *
	 * @param date 日期对象
	 * @return 日期字串
	 */
	public static String get19Date(Date date) {
		return format(date, "yyyy-MM-dd HH:mm:ss");
	}

	/**
	 * 获得一个19位长度的当前日期字串
	 *
	 * @return 日期字串
	 * @see #get19Date(Date)
	 */
	public static String get19DateNow() {
		return get19Date(new Date());
	}

	/**
	 * 将指定的日期转换为23位长度的日期字串，日期格式为：yyyy-MM-dd HH:mm:ss.n。
	 *
	 * @param date 日期对象
	 * @return 日期字串
	 */
	public static String get23Date(Date date) {
		return format(date, "yyyy-MM-dd HH:mm:ss.n");
	}

	/**
	 * 获得一个23位长度的当前日期字串
	 *
	 * @return 日期字串
	 * @see #get23Date(Date)
	 */
	public static String get23DateNow() {
		return get23Date(new Date());
	}

	/**
	 * 将指定的日期转换为5位长度的时间字串，日期格式为：HH:mm。
	 *
	 * @param date 日期对象
	 * @return 时间字串
	 */
	public static String get5Time(Date date) {
		return format(date, "HH:mm");
	}

	/**
	 * 获得一个5位长度的当前时间字串
	 *
	 * @return 时间字串
	 * @see #get5Time(Date)
	 */
	public static String get5TimeNow() {
		return get5Time(new Date());
	}

	/**
	 * 将指定的日期转换为8位长度的时间字串，日期格式为：HH:mm:ss。
	 *
	 * @param date 日期对象
	 * @return 时间字串
	 */
	public static String get8Time(Date date) {
		return format(date, "HH:mm:ss");
	}

	/**
	 * 获得一个8位长度的当前时间字串
	 *
	 * @return 时间字串
	 * @see #get8Time(Date)
	 */
	public static String get8TimeNow() {
		return get8Time(new Date());
	}

	/**
	 * 合并两个Date类型的数据为一个Date对象，一般是针对日期和时间的合并。
	 *
	 * @param date 日期对象
	 * @param time 时间对象
	 * @return 包含日期和时间的Date对象
	 */
	public static Date mergeDateTime(Date date, Date time) {
		LocalDate localDate = toLocalDate(date);
		LocalTime localTime = toLocalTime(time);
		return from(mergeDateTime(localDate, localTime));
	}

	/**
	 * 合并两个Date类型的数据为一个LocalDateTime对象，一般是针对日期和时间的合并。
	 *
	 * @param localDate 日期对象
	 * @param localTime 时间对象
	 * @return 包含日期和时间的LocalDateTime对象
	 */
	public static LocalDateTime mergeDateTime(LocalDate localDate, LocalTime localTime) {
		return LocalDateTime.of(localDate, localTime);
	}

	/**
	 * 将一个日期类型转换为长整型
	 *
	 * @param date 日期
	 * @return 长整型值
	 */
	public static Long date2Long(Date date) {
		if (date == null) {
			return null;
		}
		return date.getTime();
	}

	/**
	 * 将一个长整数转换为日期类型
	 *
	 * @param time 时间
	 * @return 日期类型的值
	 */
	public static Date long2Date(Long time) {
		if (time == null) {
			return null;
		}
		return new Date(time);
	}

	/**
	 * 字段类型枚举定义
	 *
	 * @author : john.peng date : 2017/9/15
	 */
	public enum FieldType {
		/**
		 * 年
		 */
		YEAR(Calendar.YEAR),
		/**
		 * 月
		 */
		MONTH(Calendar.MONTH),
		/**
		 * 日
		 */
		DAY(Calendar.DAY_OF_MONTH),
		/**
		 * 小时
		 */
		HOUR(Calendar.HOUR_OF_DAY),
		/**
		 * 分钟
		 */
		MINUTE(Calendar.MINUTE),
		/**
		 * 秒钟
		 */
		SECOND(Calendar.SECOND),
		/**
		 * 毫秒
		 */
		MILLISECOND(Calendar.MILLISECOND);

		int field;

		FieldType(int field) {
			this.field = field;
		}

		int getField() {
			return field;
		}
	}

	/**
	 * 时间段定义，包含了long类型的时间上限和下限。
	 */
	@Setter
	@Getter
	@ToString
	public static class DatetimeRange {
		private long lowerLimit, upperLimit;

		/**
		 * 初始化一个时间段
		 * 
		 * @param lowerLimit 时间下限
		 * @param upperLimit 时间上限
		 * @return 时间段对象
		 */
		public static DatetimeRange of(long lowerLimit, long upperLimit) {
			return lowerLimit <= upperLimit ? new DatetimeRange(lowerLimit, upperLimit)
					: new DatetimeRange(upperLimit, lowerLimit);
		}

		/**
		 * 默认的构造函数
		 * 
		 * @param lowerLimit 时间下限
		 * @param upperLimit 时间上限
		 */
		private DatetimeRange(long lowerLimit, long upperLimit) {
			super();
			this.lowerLimit = lowerLimit;
			this.upperLimit = upperLimit;
		}

		/**
		 * 根据输入的参数初始化一个时间段对象
		 * 
		 * @param params 可变的初始化参数，按序分别为：年、月、日、时、分、秒；特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @return 时间段对象
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		private static DatetimeRange create(int... params) {
			if (params == null || params.length <= 0 || params.length > 6) {
				throw new IllegalArgumentException();
			}
			LocalDateTime dateTime = LocalDateTime.of(0, 1, 1, 0, 0, 0, 0), dateTime1 = null;
			int length = params.length;
			switch (length) {
			case 6:
				dateTime = dateTime.withSecond(params[5]);
			case 5:
				dateTime = dateTime.withMinute(params[4]);
			case 4:
				dateTime = dateTime.withHour(params[3]);
			case 3:
				dateTime = dateTime.withDayOfMonth(params[2]);
			case 2:
				dateTime = JANUARY_IS_ZERO ? dateTime.withMonth(params[1] + 1) : dateTime.withMonth(params[1]);
			case 1:
				dateTime = dateTime.withYear(params[0]);
				break;
			default:
				throw new UnsupportedOperationException(String.format("Unsupport %d params.", length));
			}
			switch (length) {
			case 6:
				dateTime1 = dateTime.plusSeconds(1).minusNanos(1);
				break;
			case 5:
				dateTime1 = dateTime.plusMinutes(1).minusNanos(1);
				break;
			case 4:
				dateTime1 = dateTime.plusHours(1).minusNanos(1);
				break;
			case 3:
				dateTime1 = dateTime.plusDays(1).minusNanos(1);
				break;
			case 2:
				dateTime1 = dateTime.plusMonths(1).minusNanos(1);
				break;
			case 1:
			default:
				dateTime1 = dateTime.plusYears(1).minusNanos(1);
				break;
			}
			return new DatetimeRange(from(dateTime).getTime(), from(dateTime1).getTime());
		}

		/**
		 * 返回上周时间段，周一～周日
		 *
		 * @return 周时间段
		 */
		public static DatetimeRange lastWeek() {
			return rangeWeek(LocalDate.now().minusWeeks(1));
		}

		/**
		 * 返回上月时间段
		 * 
		 * @return 月时间段
		 */
		public static DatetimeRange lastMonth() {
			LocalDate lastDate = LocalDate.now().minusMonths(1);
			return range(lastDate.getYear(), lastDate.getMonthValue());
		}

		/**
		 * 返回上年时间段
		 * 
		 * @return 年时间段
		 */
		public static DatetimeRange lastYear() {
			return range(LocalDate.now().getYear() - 1);
		}

		/**
		 * 返回当前周时间段
		 * 
		 * @return 周时间段
		 */
		public static DatetimeRange currentWeek() {
			return rangeWeek(LocalDate.now());
		}

		/**
		 * 返回当前月时间段
		 * 
		 * @return 月时间段
		 */
		public static DatetimeRange currentMonth() {
			LocalDate now = LocalDate.now();
			return range(now.getYear(), now.getMonthValue());
		}

		/**
		 * 返回当前年时间段
		 * 
		 * @return 年时间段
		 */
		public static DatetimeRange currentYear() {
			return range(LocalDate.now().getYear());
		}

		/**
		 * 返回下周时间段
		 * 
		 * @return 周时间段
		 */
		public static DatetimeRange nextWeek() {
			return rangeWeek(LocalDate.now().plusWeeks(1));
		}

		/**
		 * 返回下月时间段
		 * 
		 * @return 月时间段
		 */
		public static DatetimeRange nextMonth() {
			LocalDate nextDate = LocalDate.now().plusMonths(1);
			return range(nextDate.getYear(), nextDate.getMonthValue());
		}

		/**
		 * 返回下年时间段
		 * 
		 * @return 年时间段
		 */
		public static DatetimeRange nextYear() {
			return range(LocalDate.now().getYear() + 1);
		}

		/**
		 * 创建指定年的时间段
		 * 
		 * @param year 年
		 * @return 年时间段
		 */
		public static DatetimeRange range(int year) {
			return create(year);
		}

		/**
		 * 创建指定年、月的时间段
		 * 
		 * @param year  年
		 * @param month 月，特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @return 月时间段
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		public static DatetimeRange range(int year, int month) {
			return create(year, month);
		}

		/**
		 * 创建指定年、月、日的时间段
		 * 
		 * @param year  年
		 * @param month 月，特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @param day   日
		 * @return 日时间段
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		public static DatetimeRange range(int year, int month, int day) {
			return create(year, month, day);
		}

		/**
		 * 获取指定时间所对应的周时间段，需要注意星期的第一天定义与FIRST_DAY_IS_MONDAY有关系。
		 * 
		 * @param localDate LocalDate对象
		 * @return 周时间段
		 * @see DateUtils#FIRST_DAY_IS_MONDAY
		 */
		private static DatetimeRange rangeWeek(LocalDate localDate) {
			LocalDateTime date0 = LocalDateTime.of(localDate, LocalTime.of(0, 0, 0, 0));
			LocalDateTime date1 = LocalDateTime.of(localDate, LocalTime.of(23, 59, 59, 999999999));
			int days = date0.getDayOfWeek().getValue();
			if (!FIRST_DAY_IS_MONDAY && days == 7) {
				days = 0;
			}
			if (FIRST_DAY_IS_MONDAY) {
				days -= 1;
			}
			date0 = date0.minusDays(days);
			date1 = date0.plusDays(7).minusNanos(1);
			return new DatetimeRange(from(date0).getTime(), from(date1).getTime());
		}

		/**
		 * 获取指定时间所对应的周时间段
		 * 
		 * @param year  年
		 * @param month 月，特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @param day   日
		 * @return 周时间段
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		public static DatetimeRange rangeWeek(int year, int month, int day) {
			month = JANUARY_IS_ZERO ? month + 1 : month;
			return rangeWeek(LocalDate.of(year, month, day));
		}

		/**
		 * 创建指定年、月、日、小时的时间段
		 * 
		 * @param year  年
		 * @param month 月，特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @param day   日
		 * @param hour  小时
		 * @return 小时时间段
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		public static DatetimeRange range(int year, int month, int day, int hour) {
			return create(year, month, day, hour);
		}

		/**
		 * 创建指定年、月、日、小时、分的时间段
		 * 
		 * @param year   年
		 * @param month  月，特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @param day    日
		 * @param hour   小时
		 * @param minute 分
		 * @return 分钟时间段
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		public static DatetimeRange range(int year, int month, int day, int hour, int minute) {
			return create(year, month, day, hour, minute);
		}

		/**
		 * 创建指定年、月、日、小时、分、秒的时间段
		 * 
		 * @param year   年
		 * @param month  月，特别要注意月的值跟JANUARY_IS_ZERO有关系。
		 * @param day    日
		 * @param hour   小时
		 * @param minute 分
		 * @param second 秒
		 * @return 秒时间段
		 * @see DateUtils#JANUARY_IS_ZERO
		 */
		public static DatetimeRange range(int year, int month, int day, int hour, int minute, int second) {
			return create(year, month, day, hour, minute, second);
		}
	}
}
